package com.ac.scum.log.web.model.login.service;

import com.ac.common.core.exception.BaseException;
import com.ac.common.core.exception.config.ExceptionType;
import com.ac.scum.log.bean.AdminUser;
import com.ac.scum.log.bean.UserPreference;
import com.ac.scum.log.web.dto.login.LoginDTO;
import com.ac.scum.log.web.model.menu.service.MenuService;
import com.ac.scum.log.web.model.user.mapper.AdminUserMapper;
import com.ac.scum.log.web.model.user.mapper.UserPreferenceMapper;
import com.ac.scum.log.web.utils.user.CurrentUserUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@AllArgsConstructor
public class LoginService {
    private static final String USER_KEY_PREFIX = "USER::";
    private static final String PASS_ERROR_COUNT_KEY_PREFIX = "PASS_ERROR_COUNT::";

    private final AdminUserMapper adminUserMapper;
    private final EncodingSalt encodingSalt;
    private final RedisTemplate<String, Object> redisTemplate;
    private final UserPreferenceMapper userPreferenceMapper;
    private final MenuService menuService;


    private void updateRedisAfterLogin(String phone, String token, LoginDTO loginDTO, Boolean isLong) {
        redisTemplate.delete(PASS_ERROR_COUNT_KEY_PREFIX + phone);
        if (isLong) {
            redisTemplate.opsForValue().set(token, loginDTO);
        } else {
            redisTemplate.opsForValue().set(token, loginDTO, 1, TimeUnit.HOURS);

        }
    }

    public LoginDTO login(AdminUser adminUser, Boolean isLong) throws BaseException {
        Assert.notNull(adminUser, "AdminUser cannot be null");
        Assert.hasText(adminUser.getPhone(), "Phone cannot be empty");

        String token = getTokenFromRedis(adminUser.getPhone());
        if (token != null) {
            redisTemplate.delete(token);
            redisTemplate.delete(USER_KEY_PREFIX + adminUser.getPhone());
        }
        token = generateAndStoreToken(adminUser, isLong);
        cleanSensitiveInformation(adminUser);
        LoginDTO loginDTO = buildLoginDTO(adminUser, token);
        updateRedisAfterLogin(adminUser.getPhone(), token, loginDTO, isLong);
        log.info("{} 登录成功", adminUser.getPhone());
        return loginDTO;
    }

    private String getTokenFromRedis(String phone) {
        return (String) redisTemplate.opsForValue().get(USER_KEY_PREFIX + phone);
    }

    private String generateAndStoreToken(AdminUser adminUser, Boolean isLong) {
        String token = JWTUtil.genAccessToken(adminUser.getPhone());
        if (isLong) {
            redisTemplate.opsForValue().set(USER_KEY_PREFIX + adminUser.getPhone(), token);
        } else {
            redisTemplate.opsForValue().set(USER_KEY_PREFIX + adminUser.getPhone(), token, 1, TimeUnit.HOURS);
        }
        return token;
    }

    private void cleanSensitiveInformation(AdminUser adminUser) {
        adminUser.setPassword(null);
        adminUser.setSalt(null);
    }

    private LoginDTO buildLoginDTO(AdminUser adminUser, String token) {
        LoginDTO loginDTO = new LoginDTO();
        loginDTO.setAdminUser(adminUser);
        loginDTO.setToken(token);
        UserPreference userPreference = userPreferenceMapper.selectOne(Wrappers.lambdaQuery(UserPreference.class).eq(UserPreference::getUserId, adminUser.getId()));
        if (userPreference.getLastCheckServerId() != null) {
            loginDTO.setMenus(menuService.findMenuByServerId(userPreference.getLastCheckServerId()));
        }
        loginDTO.setUserPreference(userPreference);
        loginDTO.setIsInitPassword(isInitPassword(adminUser.getPhone()));
        return loginDTO;
    }

    public Boolean isInitPassword(String phone) {
        AdminUser user = adminUserMapper.findUserByPhone(phone);
        return user.getPassword().equals("91c35a779eae2b598780720c24a09609");
    }

    private void incrementPasswordErrorCount(String phone) {
        Integer errorCount = (Integer) redisTemplate.opsForValue().get(PASS_ERROR_COUNT_KEY_PREFIX + phone);
        if (errorCount == null) {
            errorCount = 1;
        } else {
            errorCount++;
        }
        redisTemplate.opsForValue().set(PASS_ERROR_COUNT_KEY_PREFIX + phone, errorCount, 1, TimeUnit.HOURS);
        Long currentTime = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); // 1小时后过期
        redisTemplate.opsForValue().set("PASS_ERROR_GT_5_FREEZE_TIME::" + phone, currentTime, 5, TimeUnit.MINUTES);
    }

    private void checkPasswordFreeze(String phone) throws BaseException {
        Object freezeStatus = redisTemplate.opsForValue().get("PASS_ERROR_GT_5_FREEZE::" + phone);
        if (freezeStatus != null) {
            Long freezeTime = (Long) redisTemplate.opsForValue().get("PASS_ERROR_GT_5_FREEZE_TIME::" + phone);
            if (freezeTime != null && freezeTime > System.currentTimeMillis()) {
                throw new BaseException(ExceptionType.Password_Error_Count_Gt_5);
            }
        }
    }

    public LoginDTO login(String phone, String password, Boolean isLong) throws BaseException {
        if (phone == null || password == null) {
            throw new BaseException(ExceptionType.Password_Error);
        }
        checkPasswordFreeze(phone);

        AdminUser adminUser = adminUserMapper.findUserByPhone(phone);
        if (adminUser == null) {
            incrementPasswordErrorCount(phone);
            throw new BaseException(ExceptionType.Password_Error);
        }

        EncodingSalt.EncodingBean encoding = encodingSalt.encoding(password, adminUser.getSalt());
        if (!adminUser.getPassword().equals(encoding.getAfter())) {
            incrementPasswordErrorCount(phone);
            throw new BaseException(ExceptionType.Password_Error);
        }

        return login(adminUser, isLong);
    }


    public Boolean logout() throws BaseException {
        redisTemplate.delete("USER::" + CurrentUserUtil.getCurrentLoginUserPhone());
        redisTemplate.delete(CurrentUserUtil.getCurrentToken());
        return Boolean.TRUE;
    }
}
